package com.attempt.grab.service;

import cn.hutool.core.thread.ThreadUtil;
import com.alibaba.fastjson.JSON;
import com.attempt.grab.constant.Payload;
import com.attempt.grab.constant.WebSocketSessionSender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author xuhaifeng
 * @since 2022年02月25日 下午 1:44
 */
@Slf4j
@Service
@SuppressWarnings("unused")
public class RobotService {
    /**
     * 远程服务端的屏幕宽
     */
    private int remoteImageWide;
    /**
     * 远程服务端的屏幕高
     */
    private int remoteImageHigh;
    private final AtomicReference<Robot> robot = new AtomicReference<>();
    private final AtomicReference<Rectangle> rectangle = new AtomicReference<>();
    /**
     * 记录最后一后键盘或鼠标事件的到达时间
     */
    private Long lasActionTime = System.currentTimeMillis();
    private static final String JPG = "jpg";
    private static final String KEY_UP = "keyup";
    private static final String KEY_DOWN = "keydown";
    private static final String MOUSE_UP = "mouseup";
    private static final String MOUSE_DOWN = "mousedown";
    private static final String MOUSE_MOVE = "mousemove";

    public RobotService() {
        try {
            robot.set(new Robot());
            final Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
            rectangle.set(new Rectangle(0, 0, (int) dimension.getWidth(), (int) dimension.getHeight()));
        } catch (AWTException e) {
            e.printStackTrace();
        }
    }

    /**
     * 进行截图任务的处理，如果有客户端连接上来，将进行截图并广播发送给所有的客户端
     */
    @SuppressWarnings("InfiniteLoopStatement")
    public void startCaptureTask() {
        while (true) {
            try {
                /* 100毫秒检查一次，如果有客户端，并且满足需要截图的条件，就截图一张发给所有的客户端，可以调整这个值，值越小延迟越小 */
                ThreadUtil.sleep(50, TimeUnit.MILLISECONDS);
                final Map<String, WebSocketSessionSender> senderMap = GrabService.getSenderMap();
                /* 没有服务连上的话，直接就退出本轮循环，不需要进行截图处理 */
                if (senderMap.size() == 0) {
                    continue;
                }
                /* 如果超过5秒没有收到键盘或鼠标事件，说明可以停止截图给客户端，节省性能。 */
                if ((System.currentTimeMillis() - lasActionTime) > 5000) {
                    continue;
                }
                final byte[] data = getCapture(robot.get(), rectangle.get());
                final ImageIcon icon = new ImageIcon(data);
                remoteImageWide = icon.getIconWidth();
                remoteImageHigh = icon.getIconHeight();
                /* 遍历发送给所有的客户端连接 */
                senderMap.values().forEach(sessionSender -> sessionSender.sendData(data));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 得到屏幕截图数据
     *
     * @return CaptureByteArray
     */
    private byte[] getCapture(final Robot robot, final Rectangle rectangle) {
        final BufferedImage bufferedImage = robot.createScreenCapture(rectangle);
        final ByteArrayOutputStream output = new ByteArrayOutputStream();
        try {
            ImageIO.write(bufferedImage, JPG, output);
            /* 进行图片压缩，图片尺寸不变，压缩图片文件大小outputQuality实现,参数1为最高质量 */
            /*Thumbnails.of(bufferedImage).scale(1f).outputQuality(0.50f).outputFormat(JPG).toOutputStream(output);*/
        } catch (IOException e) {
            log.error("图片写入出现异常", e);
        }
        return output.toByteArray();
    }

    /**
     * 回放处理客户端发送过来的键盘或鼠标事件
     *
     * @param payload payload
     */
    @SuppressWarnings("DuplicatedCode")
    public void actionEvent(final Payload payload) {
        if (KEY_DOWN.equals(payload.getInputType())) {
            log.info("键盘按下事件,{}", JSON.toJSONString(payload));
            robot.get().keyPress(changeKeyCode(payload.getKeyCode()));
        } else if (KEY_UP.equals(payload.getInputType())) {
            log.info("键盘弹起事件,{}", JSON.toJSONString(payload));
            robot.get().keyRelease(changeKeyCode(payload.getKeyCode()));
        } else {
            //这里为什么要这样转？说明如下：
            //假如浏览器的image区域为1200*800,远程桌面的截图区为900*700
            //那么在浏览器上点击了clientX=77,clientY=88这个坐标时，实际上在远程
            //桌面上正确的坐标应该为：
            //remoteClientX = clientX * remoteImageWidth/imageWidth;
            //即：remoteClientX = 77 * 900 / 1200
            //remoteClientY同理.
            int remoteClientX = payload.getClientX() * remoteImageWide / payload.getImageWidth();
            int remoteClientY = payload.getClientY() * remoteImageHigh / payload.getImageHeight();
            if (MOUSE_DOWN.equals(payload.getInputType())) {
                log.info("鼠标按下事件,{}", JSON.toJSONString(payload));
                robot.get().mouseMove(remoteClientX, remoteClientY);
                /* 执行鼠标的按下 */
                if (payload.getMouse() == 0) {
                    /* 左键 */
                    robot.get().mousePress(1 << 4);
                } else if (payload.getMouse() == 1) {
                    /* 中间键 */
                    robot.get().mousePress(1 << 3);
                } else if (payload.getMouse() == 2) {
                    /* 右键 */
                    robot.get().mousePress(1 << 2);
                }
            } else if (MOUSE_UP.equals(payload.getInputType())) {
                log.info("鼠标弹起事件,{}", JSON.toJSONString(payload));
                robot.get().mouseMove(remoteClientX, remoteClientY);
                /* 执行鼠标弹起 */
                if (payload.getMouse() == 0) {
                    /* 左键 */
                    robot.get().mouseRelease(1 << 4);
                } else if (payload.getMouse() == 1) {
                    /* 中间键 */
                    robot.get().mouseRelease(1 << 3);
                } else if (payload.getMouse() == 2) {
                    /* 右键 */
                    robot.get().mouseRelease(1 << 2);
                }

            } else if (MOUSE_MOVE.equals(payload.getInputType())) {
                log.info("鼠标移动事件,{}", JSON.toJSONString(payload));
                robot.get().mouseMove(remoteClientX, remoteClientY);
            }
        }
    }

    /**
     * 进行keyCode的改变，因为浏览器的键盘事件和Java的awt的事件代码，有些是不一样的，需要进行转换，
     * 比如浏览器中13表示回车，但在Java的awt中是用10表示
     * 这里可能转换不全，比如F1-F12键都没有处理，因为浏览器现在没有禁用这些键，如果需要支持，可以继续在这里加上
     *
     * @param sourceKeyCode
     * @return
     */
    @SuppressWarnings("AlibabaUndefineMagicConstant")
    private int changeKeyCode(int sourceKeyCode) {
        //回车
        if (sourceKeyCode == 13) {return 10;}
        //,< 188 -> 44
        if (sourceKeyCode == 188) {return 44;}
        //.>在Js中为190，但在Java中为46
        if (sourceKeyCode == 190) {return 46;}
        // /?在Js中为191，但在Java中为47
        if (sourceKeyCode == 191) {return 47;}
        //;: 186 -> 59
        if (sourceKeyCode == 186) {return 59;}
        //[{ 219 -> 91
        if (sourceKeyCode == 219) {return 91;}
        //\| 220 -> 92
        if (sourceKeyCode == 220) {return 92;}
        //-_ 189->45
        if (sourceKeyCode == 189) {return 45;}
        //=+ 187->61
        if (sourceKeyCode == 187) {return 61;}
        //]} 221 -> 93
        if (sourceKeyCode == 221) {return 93;}
        //DEL
        if (sourceKeyCode == 46) {return 127;}
        //Ins
        if (sourceKeyCode == 45) {return 155;}
        return sourceKeyCode;
    }

    public int getRemoteImageWidth() {
        return remoteImageWide;
    }

    public void setRemoteImageWidth(int remoteImageWide) {
        this.remoteImageWide = remoteImageWide;
    }

    public int getRemoteImageHigh() {
        return remoteImageHigh;
    }

    public void setRemoteImageHigh(int remoteImageHigh) {
        this.remoteImageHigh = remoteImageHigh;
    }

    public Long getLasActionTime() {
        return lasActionTime;
    }

    public void setLasActionTime(Long lasActionTime) {
        this.lasActionTime = lasActionTime;
    }
}
